function Vue(options) {
  // this --->  vm
  let _data = (this.$data = options.data);
  /*
    数据代理：
        将data里的数据，放到vm上，放到vm上为了我们在组件中获取的时候，直接通过this.xxx获取数据
        内部使用的是Object.defineproperty实现的代理
         Object.keys(_data)  ['msg','user']
  */
  Object.keys(_data).forEach((key) => {
    this.proxy(key);
  });
  /*
    数据劫持：
      将data里数据变成响应式的(get/set)，我们获取数据的时候，会触发get方法，在get方法里可以进行依赖收集
        修改数据的时候，会触发set方法，在set方法里或触发依赖更新
        它会通过递归调用，实现深层次数据劫持。
  */
  observe(_data);
  compile(options.el, this);
}
// 数据代理
Vue.prototype.proxy = function (key) {
  Object.defineProperty(this, key, {
    get() {
      return this.$data[key];
    },
    set(newVal) {
      this.$data[key] = newVal;
    },
  });
};

// 数据劫持
function observe(data) {
  if (typeof data === "string") return;
  Object.keys(data).forEach((key) => {
    let originVal = data[key];
    // 递归调用数据劫持函数，进行深层次的数据劫持
    observe(originVal);
    Object.defineProperty(data, key, {
      get() {
        // 收集依赖
        return originVal;
      },
      set(newVal) {
        originVal = newVal;
        // 触发依赖更新
      },
    });
  });
}

// 模板解析
function compile(el, vm) {
  if (typeof el === "string") {
    vm.$el = document.querySelector(el);
  } else {
    vm.$el = el;
  }
  // 创建文档碎片
  let fragment = document.createDocumentFragment(),
    child;
  // 将所有的子节点放到文档碎片里
  while ((child = vm.$el.firstChild)) {
    fragment.append(child);
  }
  // 操作文档碎片
  operationFragment(fragment, vm);
  // 将操作后的文档碎片，放回根标签
  vm.$el.append(fragment);
}
// 操作文档碎片
function operationFragment(node, vm) {
  // 获取所有的子元素
  let nodes = node.childNodes;

  if (node.nodeType === 1) {
    // 标签节点
    if (node.nodeName === "INPUT") {
      // 获取所有的标签属性
      let attrs = node.attributes;
      Array.from(attrs).forEach((attr) => {
        if (attr.nodeName === "v-model") {
          let val = attr.nodeValue
            .split(".")
            .reduce((pre, item) => pre[item], vm.$data);
          node.value = val;
        }
      });
    }
  }

  if (node.nodeType === 3) {
    // 文本节点
    let nodeVal = node.nodeValue;
    // 定义正则获取插值表达式
    let reg = /\{\{\s*(\S+)\s*\}\}/;
    let regResult = reg.exec(nodeVal);

    if (regResult) {
      // regResult[0] ---- {{ msg  }}   {{ user.name }}
      // regResult[1] ---- msg   user.name
      // 获取data里的数据
      let val = regResult[1]
        .split(".")
        .reduce((pre, item) => pre[item], vm.$data);
      node.nodeValue = nodeVal.replace(regResult[0], val);
    }
  }
  nodes.forEach((node) => operationFragment(node, vm));
}
